Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a matches!(expr, pattern) macro. #14685

Closed
wants to merge 3 commits into from

Conversation

SimonSapin
Copy link
Contributor

I’m not really attached to the name or signature, but I’d like to have some form of this in the prelude.

Edit: original title: Add a is_match!(expr, pattern) macro.

@TyOverby
Copy link
Contributor

TyOverby commented Jun 6, 2014

I wrote a matches!(pattern, expr) macro for my own personal use and have used it a few times. I think it's pretty handy.

@ftxqxd
Copy link
Contributor

ftxqxd commented Jun 6, 2014

I’ve used a similar macro a few times—assert_match!. Could this also be useful or do you think that assert!(is_match!(…)) should be enough? (It would be useful for more informative error messages…)

@Kimundi
Copy link
Member

Kimundi commented Jun 6, 2014

1+, I wanted something like that for ages.

@@ -322,6 +322,14 @@ macro_rules! vec(
($($e:expr),+,) => (vec!($($e),+))
)

/// Return whether the given expression matches the given pattern, as a bool.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"the given pattern(s)"

Also, it would be good to have an example or two.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@SimonSapin
Copy link
Contributor Author

@P1start I think assert!(is_match!(…)) is enough. You can always specify the error message as a second argument to assert!(). If we did add more assert_foo!() macros, we might also want debug_assert_foo!(), and they would proliferate quickly.

@SimonSapin SimonSapin changed the title Add a is_match!(expr, pattern) macro. Add a matches!(expr, pattern) macro. Jun 12, 2014
@boschni
Copy link

boschni commented Jun 27, 2014

I'm all in for adding this macro! How about also supporting guards?

macro_rules! matches(
    ($e: expr, $($p :pat)|*) => (match $e {
        $($p)|+ => true,
        _ => false
    });
    ($e: expr, $($p :pat)|* if $g: expr) => (match $e {
        $($p)|+ if $g => true,
        _ => false
    });
)

@SimonSapin
Copy link
Contributor Author

@u-nikos, how is matches(expr, pat if test) different from matches(expr, pat) && test?

@boschni
Copy link

boschni commented Jun 27, 2014

@SimonSapin, it allows you to match against the content of data types:

matches!(Some("value"), Some(v) if v == "value")

@SimonSapin
Copy link
Contributor Author

I guess this doesn’t hurt. Added.

@SimonSapin
Copy link
Contributor Author

The Travis build failed because of /home/travis/build.sh: line 226: travis_wait: command not found, which seems unrelated to this PR.

@nrc
Copy link
Member

nrc commented Jul 8, 2014

This was discussed at the meeting today. The general sentiment was that we would like something to fill this gap, but no one was keen on adding another macro to the prelude. There is also talk (@kballard, I think) about adding this as a language feature. So, the conclusion was that this should go through the RFC process in order to figure out exactly what we want here. (My personal vote is for a macro, fwiw).

@nrc nrc closed this Jul 8, 2014
@lilyball
Copy link
Contributor

lilyball commented Jul 8, 2014

Apparently I never even saw this PR. It does seem that the language feature I'm currently writing up an RFC for (if let expressions) is related, although not really a drop-in replacement.

Offhand, matches!() seems like a potentially-useful macro. Even if my if let is accepted, the example given for your macro would not really be a great fit (if let is really meant for unwrapping values, not doing arbitrary pattern-matching).

@TyOverby
Copy link
Contributor

TyOverby commented Jul 8, 2014

@kballard

It sounds like they would work well together.

Is your if let RFC inspired by Swift's language feature?

@SimonSapin
Copy link
Contributor Author

@kballard Could you please link your RFC here when it’s up?

I don’t know what if let is, but in my vague guess it’s different from matches!(). The latter is a boolean expression that is especially useful in a larger expression, such as with the && operator.

@lilyball
Copy link
Contributor

lilyball commented Jul 8, 2014

@TyOverby The syntax is. The behavior is something that's been requested before (but never specified very well, and never directly tied to if statements before).

@SimonSapin Will do.

@lilyball
Copy link
Contributor

lilyball commented Jul 9, 2014

SimonSapin added a commit to SimonSapin/rust that referenced this pull request Oct 23, 2019
# Motivation

This macro is:

* General-purpose (not domain-specific)
* Simple (the implementation is short)
* Very popular [on crates.io](https://crates.io/crates/matches)
  (currently 37th in all-time downloads)
* The two previous points combined make it number one in
  [left-pad index](https://twitter.com/bascule/status/1184523027888988160)
  score

As such, I feel it is a good candidate for inclusion in the standard library.

In fact I already felt that way five years ago:
rust-lang#14685
(Although the proof of popularity was not as strong at the time.)

Back then, the main concern was that this macro may not be quite
universally-enough useful to belong in the prelude.

# API

Therefore, this PR adds the macro such that using it requires one of:

```
use core::macros::matches;
use std::macros::matches;
```

Like arms of a `match` expression,
the macro supports multiple patterns separated by `|`
and optionally followed by `if` and a guard expression:

```
let foo = 'f';
assert!(matches!(foo, 'A'..='Z' | 'a'..='z'));

let bar = Some(4);
assert!(matches!(bar, Some(x) if x > 2));
```

# Implementation constraints

A combination of reasons make it tricky
for a standard library macro not to be in the prelude.

Currently, all public `macro_rules` macros in the standard library macros
end up “in the prelude” of every crate not through `use std::prelude::v1::*;`
like for other kinds of items,
but through `#[macro_use]` on `extern crate std;`.
(Both are injected by `src/libsyntax_ext/standard_library_imports.rs`.)

`#[macro_use]` seems to import every macro that is available
at the top-level of a crate, even if through a `pub use` re-export.

Therefore, for `matches!` not to be in the prelude, we need it to be
inside of a module rather than at the root of `core` or `std`.

However, the only way to make a `macro_rules` macro public
outside of the crate where it is defined
appears to be `#[macro_export]`.
This exports the macro at the root of the crate
regardless of which module defines it.
See [macro scoping](
https://doc.rust-lang.org/reference/macros-by-example.html#scoping-exporting-and-importing)
in the reference.

Therefore, the macro needs to be defined in a crate
that is not `core` or `std`.

# Implementation

This PR adds a new `matches_macro` crate as a private implementation detail
of the standard library.
This crate is `#![no_core]` so that libcore can depend on it.
It contains a `macro_rules` definition with `#[macro_export]`.

libcore and libstd each have a new public `macros` module
that contains a `pub use` re-export of the macro.
Both the module and the macro are unstable, for now.

The existing private `macros` modules are renamed `prelude_macros`,
though their respective source remains in `macros.rs` files.
Centril added a commit to Centril/rust that referenced this pull request Oct 23, 2019
Add the `matches!( $expr, $pat ) -> bool` macro

# Motivation

This macro is:

* General-purpose (not domain-specific)
* Simple (the implementation is short)
* Very popular [on crates.io](https://crates.io/crates/matches) (currently 37th in all-time downloads)
* The two previous points combined make it number one in [left-pad index](https://twitter.com/bascule/status/1184523027888988160) score

As such, I feel it is a good candidate for inclusion in the standard library.

In fact I already felt that way five years ago: rust-lang#14685 (Although the proof of popularity was not as strong at the time.)

# API

<details>
<del>

Back then, the main concern was that this macro may not be quite universally-enough useful to belong in the prelude.

Therefore, this PR adds the macro such that using it requires one of:

```rust
use core::macros::matches;
use std::macros::matches;
```

</del>
</details>

Like arms of a `match` expression, the macro supports multiple patterns separated by `|` and optionally followed by `if` and a guard expression:

```rust
let foo = 'f';
assert!(matches!(foo, 'A'..='Z' | 'a'..='z'));

let bar = Some(4);
assert!(matches!(bar, Some(x) if x > 2));
```

<details>
<del>

# Implementation constraints

A combination of reasons make it tricky for a standard library macro not to be in the prelude.

Currently, all public `macro_rules` macros in the standard library macros end up “in the prelude” of every crate not through `use std::prelude::v1::*;` like for other kinds of items, but through `#[macro_use]` on `extern crate std;`. (Both are injected by `src/libsyntax_ext/standard_library_imports.rs`.)

`#[macro_use]` seems to import every macro that is available at the top-level of a crate, even if through a `pub use` re-export.

Therefore, for `matches!` not to be in the prelude, we need it to be inside of a module rather than at the root of `core` or `std`.

However, the only way to make a `macro_rules` macro public outside of the crate where it is defined appears to be `#[macro_export]`. This exports the macro at the root of the crate regardless of which module defines it. See [macro scoping](https://doc.rust-lang.org/reference/macros-by-example.html#scoping-exporting-and-importing) in the reference.

Therefore, the macro needs to be defined in a crate that is not `core` or `std`.

# Implementation

This PR adds a new `matches_macro` crate as a private implementation detail of the standard library. This crate is `#![no_core]` so that libcore can depend on it. It contains a `macro_rules` definition with `#[macro_export]`.

libcore and libstd each have a new public `macros` module that contains a `pub use` re-export of the macro. Both the module and the macro are unstable, for now.

The existing private `macros` modules are renamed `prelude_macros`, though their respective source remains in `macros.rs` files.

</del>
</details>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants